﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System;

namespace XEngine
{
    public class XRenderer : XComponent, XLoadable, XDrawable
    {
        public List<XActor> ActorsInView = new List<XActor>();

        RenderTarget2D DepthTarget;
        RenderTarget2D BlurTarget;

        Effect BlurEffect;
        Effect Model;

        DepthStencilBuffer DepthBuffer;

        // VSM

        Vector2[] sampleOffsetsH = new Vector2[15];
        float[] sampleWeightsH = new float[15];

        Vector2[] sampleOffsetsV = new Vector2[15];
        float[] sampleWeightsV = new float[15];

        Vector2 texelSize;

        XQuadDrawer QuadDrawer;

        public XRenderer(XMain X) : base(X) 
        {
        }

        int dbSize = 1024;

        public override void Load(Microsoft.Xna.Framework.Content.ContentManager Content)
        {
            DepthTarget = new RenderTarget2D(X.GraphicsDevice, dbSize, dbSize, 1, SurfaceFormat.Vector2);
            BlurTarget = new RenderTarget2D(X.GraphicsDevice, dbSize, dbSize, 1, SurfaceFormat.Vector2);

            BlurEffect = Content.Load<Effect>(@"Content\XEngine\Effects\Blur");
            Model = Content.Load<Effect>(@"Content\XEngine\Effects\Model");

            texelSize = new Vector2(1.0f / dbSize, 1.0f / dbSize);

            DepthBuffer = new DepthStencilBuffer(X.GraphicsDevice, dbSize, dbSize, DepthFormat.Depth24);

            DepthCam = new XCamera(X);

            QuadDrawer = new XQuadDrawer(X);
            QuadDrawer.Load(Content);

            NoDepthDraw.Add(X.DebugDrawer);
            NoDepthDraw.Add(X.Debug);
            NoDepthDraw.Add(X.Console);

            base.Load(Content);
        }

        int pass = 1;

        XCamera DepthCam;

        public void DrawWithBall(GameTime gameTime, XCamera Camera, XActor Ball)
        {
            this.Ball = Ball;
            this.BallPos = Ball.Position;

            Draw(gameTime, Camera);
        }

        Vector3 BallPos;
        XActor Ball;

        List<XComponent> NoDepthDraw = new List<XComponent>();

        public override void Draw(GameTime gameTime, XCamera Camera)
        {
            DepthStencilBuffer old = X.GraphicsDevice.DepthStencilBuffer;

            X.GraphicsDevice.DepthStencilBuffer = DepthBuffer;

            DepthCam.Position = LightPosition;
            DepthCam.Target = Vector3.Zero + BallPos;

            CameraPosition = Camera.Position;

            // Depth Map
            X.GraphicsDevice.SetRenderTarget(0, DepthTarget);
            X.GraphicsDevice.Clear(Color.Black);
            DrawScene(gameTime, DepthCam, NoDepthDraw);

            pass++;

            // Blur
            X.GraphicsDevice.SetRenderTarget(0, BlurTarget);
            BlurShadow();

            // Draw
            X.GraphicsDevice.SetRenderTarget(0, null);
            X.GraphicsDevice.DepthStencilBuffer = old;
            X.GraphicsDevice.Clear(Color.CornflowerBlue);
            DrawScene(gameTime, Camera, null);

            //X.spriteBatch.Begin();
            //X.spriteBatch.Draw(DepthTarget.GetTexture(), new Rectangle(0, 0, 512, 512), Color.White);
            //X.spriteBatch.Draw(BlurTarget.GetTexture(), new Rectangle(512, 0, 512, 512), Color.White);
            //X.spriteBatch.End();

            pass = 1;
        }

        void BlurShadow()
        {
            BlurEffect.Parameters["input"].SetValue(DepthTarget.GetTexture());

            BlurEffect.Begin();
            BlurEffect.CurrentTechnique.Passes[0].Begin();

            QuadDrawer.Draw(null, null);

            BlurEffect.CurrentTechnique.Passes[0].End();
            BlurEffect.End();
        }

        public void DrawScene(GameTime gameTime, XCamera Camera, List<XComponent>NoDraw)
        {
            X.GraphicsDevice.RenderState.DepthBufferEnable = true;
            X.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            X.GraphicsDevice.RenderState.AlphaBlendEnable = false;

            List<List<XComponent>> draw = new List<List<XComponent>>();

            foreach (XComponent component in X.Components)
            {
                bool allow = true;
                
                if (NoDraw != null)
                    if (NoDraw.Contains(component))
                        allow = false;

                if (allow && component.AutoDraw && component is XDrawable && !(component is XRenderer))
                {
                    if (component.DrawOrder > draw.Count)
                    {
                        List<XComponent> level = new List<XComponent>();
                        level.Add(component);
                        draw.Add(level);
                    }
                    else
                        draw[component.DrawOrder - 1].Add(component);
                }
            }

            List<XActor> Alpha = new List<XActor>();

            ActorsInView.Clear();
            foreach (List<XComponent> level in draw)
            {
                foreach (XComponent component in level)
                {
                    if (component is XActor)
                    {
                        if (Camera.Frustrum.Contains(((XActor)component).boundingBox) != ContainmentType.Disjoint || component.NoCull)
                        {
                            ActorsInView.Add(((XActor)component));

                            if (((XActor)component).AlphaBlendAble)
                                Alpha.Add(((XActor)component));
                            else
                                component.Draw(gameTime, Camera);
                        }
                    }
                    else if (component is XHeightMap)
                    {
                        if (Camera.Frustrum.Contains(((XHeightMap)component).boundingBox) != ContainmentType.Disjoint || component.NoCull)
                            component.Draw(gameTime, Camera);
                    }
                    else if (component is XWater)
                    {
                        if (Camera.Frustrum.Contains(((XWater)component).boundingBox) != ContainmentType.Disjoint || component.NoCull)
                            component.Draw(gameTime, Camera);
                    }
                    else
                        component.Draw(gameTime, Camera);
                }

                foreach (XActor actor in Alpha)
                    actor.Draw(gameTime, Camera);
            }
            X.spriteBatch.Begin();

            if (X.Console.Visible)
            {
                if (NoDraw != null)
                {
                    if (!NoDraw.Contains(X.Console))
                        X.Console.Draw(gameTime, Camera);
                }
                else
                    X.Console.Draw(gameTime, Camera);
            }
            else
            {
                if (NoDraw != null)
                {
                    if (!NoDraw.Contains(X.Debug))
                        X.Debug.Draw(gameTime, Camera);
                }
                else
                    X.Debug.Draw(gameTime, Camera);
            }

            X.spriteBatch.End();
        }

        public virtual void DrawModel(XModel Model, XCamera Camera, Matrix[] World)
        {
            foreach (ModelMesh mesh in Model.Model.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["World"].SetValue(World[0]);
                    //effect.Parameters["Bones"].SetValue(bones);

                    if (pass == 1)
                    {
                        effect.CurrentTechnique = effect.Techniques["Depth"];
                        effect.Parameters["View"].SetValue(DepthCam.View);
                        effect.Parameters["Projection"].SetValue(DepthCam.Projection);
                    }
                    else
                    {
                        effect.CurrentTechnique = effect.Techniques["Model"];
                        effect.Parameters["View"].SetValue(Camera.View);
                        effect.Parameters["Projection"].SetValue(Camera.Projection);
                        effect.Parameters["vecEye"].SetValue(new Vector4(Camera.Position, 1));
                        effect.Parameters["WorldLightViewProjection"].SetValue(World[0] * DepthCam.View * DepthCam.Projection);
                        effect.Parameters["ModelPos"].SetValue(BallPos);

                        if (Model == Ball.model)
                            effect.Parameters["SelfShadow"].SetValue(false);
                        else
                            effect.Parameters["SelfShadow"].SetValue(true);
                    }

                    SetupLighting(effect);
                }
                mesh.Draw();
            }
        }

        Vector3 LightPosition;
        Vector3 CameraPosition;

        public void SetupLighting(Effect effect)
        {
            Vector4[] LightDir = {
                        new Vector4(-0.526f, 0.573f, -0.627f, 1),
                        new Vector4(0.719f, 0.342f, 0.604f, 1),
                        new Vector4(0.454f, 0.766f, 0.454f, 1)
                    };

            Vector4[] LightColor = {
                        new Vector4(.8f, .8f, .8f, 10000000f),
                        new Vector4(.8f, .8f, .8f, 10f),
                        new Vector4(.8f, .8f, .8f, 10000000f)
                    };

            if (pass == 2)
            {


                effect.Parameters["vecLightDir"].SetValue(LightDir);

                effect.Parameters["LightColor"].SetValue(LightColor);
                effect.Parameters["NumLights"].SetValue(LightDir.Length);

                effect.Parameters["ShadowMap"].SetValue(BlurTarget.GetTexture());
                effect.Parameters["LightPosition"].SetValue(LightPosition);
                effect.Parameters["CameraPosition"].SetValue(CameraPosition);
            }
            else
            {
                LightPosition = Vector3.Transform(X.Tools.ConvertVector4ToVector3(LightDir[0]), Matrix.CreateTranslation(new Vector3(5, 20, 5))) + BallPos;

                effect.Parameters["LightPosition"].SetValue(LightPosition);
            }
        }
    }
}
